﻿using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using MASAS.MSM.DataLayer.DataMapper;
using MASAS.MSM.DomainLayer.Logging;
using MASAS.MSM.DomainLayer.Model;

namespace MASAS.MSM.SharePoint.Server
{

    /// <summary>
    /// SharePoint Data store class.
    /// </summary>
    public class SharePointDataStore : IDataStore
    {

        // Internal members...
        private bool            disposed                    = false;
        private string          storageName                 = "MASAS SharePoint 2010 Storage";
        private SharePointHub   _currentHub                 = null;

        // SharePoint List Names
        private const string _masasHubConnectionsListName   = "MASAS Hub Connections";
        private const string _masasHubItemsListName         = "MASAS Hub Items";
        private const string _masasHubFiltersListName       = "MASAS Hub Filters";
        private const string _masasNotificationsListName    = "MASAS Notifications";
        private const string _masasPublicationsListName     = "MASAS Publications";

        // SharePoint Internal Field Names
        private const string _spTitleField                  = "Title";
        private const string _spIDField                     = "ID";

        private const string _masasHubItemIDField           = "MASAS_HubItemID";
        private const string _masasTrackingStateField       = "MASAS_TrackingState";
        private const string _masasSymbolIDField            = "MASAS_SymbolID";
        private const string _masasSummaryField             = "MASAS_Summary";
        private const string _masasSourceItemIDField        = "MASAS_SourceItemID";
        private const string _masasSourceField              = "MASAS_Source";
        private const string _masasSeverityField            = "MASAS_Severity";
        private const string _masasRawDataXMLField          = "MASAS_RawDataXML";
        private const string _masasLongitudeField           = "MASAS_Longitude";
        private const string _masasLatitudeField            = "MASAS_Latitude";
        private const string _masasLastUpdatedField         = "MASAS_LastUpdatedDTG";
        private const string _masasInitialLoadField         = "MASAS_InitialLoadDTG";
        private const string _masasExternalSourceIDField    = "MASAS_ExternalSourceID";
        private const string _masasDIGESTField              = "MASAS_DIGEST";
        private const string _masasEnclosureXMLField        = "MASAS_EnclosureXML";
        private const string _masasMessageStatusField       = "MASAS_MessageStatus";
        private const string _masasCategoryField            = "MASAS_Category";
        private const string _masasExpirationField          = "MASAS_Expiration";
        
        private const string _masasURIField                 = "URI";
        private const string _masasTokenField               = "Token";
        private const string _masasAccessRightsField        = "AccessRights";
        private const string _masasLastAccessedField        = "LastAccessed";

        private const string _masasFilterDescriptionField   = "Filter_x0020_Description";
        private const string _masasFilterTypeField          = "Filter_x0020_Type";
        private const string _masasFilterPriorityField      = "Filter_x0020_Priority";
        private const string _masasFilterField              = "Filter";
        private const string _masasFilterEnabledField       = "Filter_x0020_Enabled";

        /// <summary>
        /// Gets the name of the storage.
        /// </summary>
        /// <value>
        /// The name of the storage.
        /// </value>
        public string StorageName {
            get { return storageName; }
        }

                /// <summary>
        /// Gets or sets the current hub.
        /// </summary>
        /// <value>The current hub.</value>
        public Hub CurrentHub {
            get { return _currentHub; }
            set
            {
                if( value is SharePointHub )
                {
                    _currentHub = value as SharePointHub;
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SharePointDataStore"/> class.
        /// </summary>
        public SharePointDataStore()
        {
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="MessageHandler"/> is reclaimed by garbage collection.
        /// </summary>
        ~SharePointDataStore()
        {
            // Simply call Dispose(false).
            Dispose( false );
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose( true );
            GC.SuppressFinalize( this );
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose( bool disposing )
        {
            if( !disposed )
            {
                if( disposing )
                {
                    // Free other state (managed objects).
                }

                // Free your own state (unmanaged objects).
                // Set large fields to null.

                disposed = true;
            }
        }

        /// <summary>
        /// Entries the exists.
        /// </summary>
        /// <param name="ID">The ID.</param>
        /// <returns></returns>
        public bool EntryExists( Guid ID )
        {
            bool retValue = false;

            using( SPSite site = new SPSite( _currentHub.SiteUrl ) )
            {
                using( SPWeb web = site.OpenWeb() )
                {
                    SPList trackableItems = web.Lists.TryGetList( _masasHubItemsListName );
                    SPListItem item = trackableItems.GetItemByUniqueId( ID );

                    if( item != null )
                    {
                        retValue = true;
                    }
                }

            }

            return retValue;
        }

        /// <summary>
        /// Entry exists by external ID.
        /// </summary>
        /// <param name="externalID">The external ID.</param>
        /// <returns>
        /// true if entry has been found by it's external ID (string)
        /// </returns>
        public bool EntryExistsByExternalID( string externalID )
        {
            bool retValue = false;

            using( SPSite site = new SPSite( _currentHub.SiteUrl ) )
            {
                using( SPWeb web = site.OpenWeb() )
                {

                    SPList trackableItems = web.Lists.TryGetList( _masasHubItemsListName );

                    if( trackableItems != null )
                    {
                        SPQuery itemQuery = new SPQuery();

                        itemQuery.Query = "<Where><Eq><FieldRef Name = \"" + _masasSourceItemIDField + "\"/>" +
                                        "<Value Type = \"Text\">" + externalID + "</Value></Eq></Where>";
                        SPListItemCollection items = trackableItems.GetItems( itemQuery );

                        if( items.Count > 0 )
                        {
                            retValue = true;
                        }
                    }
                }
            }

            return retValue;
        }

        /// <summary>
        /// Adds the entry.
        /// </summary>
        /// <param name="Entry">The entry.</param>
        /// <returns>
        /// True if successful, false otherwise
        /// </returns>
        public bool AddEntry( HubEntry entry )
        {
            bool retValue = false;

            using( SPSite site = new SPSite( _currentHub.SiteUrl ) )
            {
                using( SPWeb web = site.OpenWeb() )
                {
                    SPList trackableItems = web.Lists.TryGetList( _masasHubItemsListName );

                    if( trackableItems != null )
                    {
                        SPListItem newItem = trackableItems.Items.Add();
                        newItem[_masasHubItemIDField] = System.Guid.NewGuid().ToString();

                        ModelToData( entry, newItem );

                        try
                        {
                            newItem.Update();
                            retValue = true;
                        }
                        catch( Exception ex )
                        {
                            Logger.AddLogEntry( "Could not create Entry: " + entry.ExternalIdentifier, ex );
                        }
                    }

                }
            }

            return retValue;
        }

        /// <summary>
        /// Updates the Hub entry.
        /// </summary>
        /// <param name="entry">The Hub entry.</param>
        /// <returns>
        /// True if entry has been updated, false otherwise.
        /// </returns>
        public bool UpdateEntry( HubEntry entry )
        {
            bool retValue = false;

            using( SPSite site = new SPSite( _currentHub.SiteUrl ) )
            {
                using( SPWeb web = site.OpenWeb() )
                {
                    SPList trackableItems = web.Lists.TryGetList( _masasHubItemsListName );

                    if( trackableItems != null )
                    {
                        SPQuery itemQuery = new SPQuery();

                        itemQuery.Query = "<Where><Eq><FieldRef Name = \"" + _masasSourceItemIDField + "\"/>" +
                                        "<Value Type = \"Text\">" + entry.ExternalIdentifier + "</Value></Eq></Where>";

                        SPListItemCollection items = trackableItems.GetItems( itemQuery );

                        if( items.Count > 0 )
                        {
                            SPListItem newItem = items[0];

                            // Since this is an update we want to preserve the tracking state that a user may have set previously.
                            // For example, if a user flipped the state to "Ignore" for an item then we do not want an update to 
                            // an item to make it appear on the map again.  Keep it's flipped state.
                            if( newItem[_masasTrackingStateField].Equals( "Action" ) ) {
                                entry.FilterState = FilterStateType.Action;
                            }
                            else if( newItem[_masasTrackingStateField].Equals( "Ignore" ) ) {
                                entry.FilterState = FilterStateType.Ignore;
                            }
                            else if( newItem[_masasTrackingStateField].Equals( "Monitor" ) ) {
                                entry.FilterState = FilterStateType.Monitor;
                            }
                            else if( newItem[_masasTrackingStateField].Equals( "None" ) ) {
                                entry.FilterState = FilterStateType.None;
                            }

                            // Convert the entry's data to the existing item...
                            ModelToData( entry, newItem );

                            try
                            {
                                newItem.Update();
                                retValue = true;
                            }
                            catch( Exception ex )
                            {
                                Logger.AddLogEntry( "Hub item could not be updated: " + entry.ExternalIdentifier, ex );
                            }
                        }
                        else
                        {
                            Logger.AddLogEntry( "Update fail! Entry no longer exists: " + entry.ExternalIdentifier, LogEventType.Error );
                        }
                    }
                }
            }

            return retValue;
        }

        /// <summary>
        /// Updates the hub data.
        /// </summary>
        /// <param name="hub">The hub.</param>
        /// <returns>
        /// True if the data has been updated, false otherwise.
        /// </returns>
        public bool UpdateHubData( Hub hub )
        {
            bool retValue = false;

            if( hub is SharePointHub )
            {
                SharePointHub spHub = hub as SharePointHub;

                using( SPSite site = new SPSite( _currentHub.SiteUrl ) )
                {
                    using( SPWeb web = site.OpenWeb() )
                    {

                        SPList hubs = web.Lists.TryGetList( _masasHubConnectionsListName );
                        SPListItem hubItem = hubs.GetItemById( spHub.ItemIdentifier );

                        if( hubItem != null )
                        {
                            hubItem[_masasLastAccessedField] = spHub.LastPolled.ToLocalTime();
                            hubItem.Update();
                            try
                            {
                                hubItem.Update();
                                retValue = true;
                            }
                            catch( Exception ex )
                            {
                                Logger.AddLogEntry( "Hub item could not be updated: " + hubItem.ID, ex );
                            }
                        }
                        else
                        {
                            Logger.AddLogEntry( "Update fail! Hub no longer exists: " + hubItem.ID, LogEventType.Error );
                        }
                    }
                }
            }

            return retValue;
        }

        /// <summary>
        /// Gets the entry of the given ID.
        /// </summary>
        /// <param name="ID">The ID of the entry.</param>
        /// <returns>
        /// The entry if found, null of not found.
        /// </returns>
        public HubEntry GetEntry( Guid ID )
        {
            HubEntry hubEntry = null;

            using( SPSite site = new SPSite( _currentHub.SiteUrl ) )
            {
                using( SPWeb web = site.OpenWeb() )
                {
                    SPList trackableItems = web.Lists.TryGetList( _masasHubItemsListName );
                    SPListItem item = trackableItems.GetItemByUniqueId( ID );

                    if( item != null )
                    {
                        hubEntry = new HubEntry();
                        DataToModel( item, hubEntry );
                    }
                }

            }

            return hubEntry;
        }

        /// <summary>
        /// Models to data.
        /// </summary>
        /// <param name="masasEntry">The masas entry.</param>
        /// <param name="trackableItem">The trackable item.</param>
        private void ModelToData( HubEntry masasEntry, SPListItem trackableItem )
        {
            trackableItem[_spTitleField]            = masasEntry.Title;
            trackableItem[_masasSourceItemIDField]  = masasEntry.ExternalIdentifier;
            trackableItem[_masasRawDataXMLField]    = masasEntry.FeedEntryXML;
            trackableItem[_masasLatitudeField]      = masasEntry.Location.Latitude.ToString();
            trackableItem[_masasLongitudeField]     = masasEntry.Location.Longitude.ToString();
            trackableItem[_masasDIGESTField]        = masasEntry.Digest;
            trackableItem[_masasSummaryField]       = masasEntry.Summary;
            trackableItem[_masasSymbolIDField]      = masasEntry.Symbol;
            trackableItem[_masasMessageStatusField] = masasEntry.Status.ToString();

            if( masasEntry.Categories.Count > 0 )
            {
                trackableItem[_masasCategoryField]  = masasEntry.Categories[0].ToString();
            }

            if( masasEntry.Severities.Count > 0 )
            {
                trackableItem[_masasSeverityField]  = masasEntry.Severities[0].ToString();
            }

            trackableItem[_masasEnclosureXMLField]  = masasEntry.AlertXML;
            trackableItem[_masasLastUpdatedField]   = masasEntry.LastUpdated;
            trackableItem[_masasExpirationField]    = masasEntry.Expiration;
            trackableItem[_masasTrackingStateField] = masasEntry.FilterState.ToString();
        }

        /// <summary>
        /// Convert a data object to a model object.
        /// </summary>
        /// <param name="spHubItem">The data object.</param>
        /// <param name="entry">The model object.</param>
        private void DataToModel( SPListItem spHubItem, HubEntry entry )
        {
            entry.Title                 = spHubItem.Title;
            entry.ExternalIdentifier    = spHubItem[_masasSourceItemIDField].ToString();
            entry.FeedEntryXML          = spHubItem[_masasRawDataXMLField].ToString();
            entry.Location.Latitude     = Convert.ToDouble( spHubItem[_masasLatitudeField] );
            entry.Location.Longitude    = Convert.ToDouble( spHubItem[_masasLongitudeField] );
            entry.Digest                = spHubItem[_masasDIGESTField].ToString();
            entry.Summary               = spHubItem[_masasSummaryField].ToString();
            entry.Symbol                = spHubItem[_masasSymbolIDField].ToString();
            entry.AlertXML              = spHubItem[_masasEnclosureXMLField].ToString();
            entry.Status                = ( StatusTypes )Enum.Parse( typeof( StatusTypes ), spHubItem[_masasMessageStatusField].ToString() );

            entry.Severities.Add( ( SeverityTypes )Enum.Parse( typeof( SeverityTypes ), spHubItem[_masasSeverityField].ToString() ) );
            entry.Categories.Add( ( CategoryTypes )Enum.Parse( typeof( CategoryTypes ), spHubItem[_masasCategoryField].ToString() ) );
            
            if( spHubItem[_masasLastUpdatedField] != null )
            {
                entry.LastUpdated = Convert.ToDateTime( spHubItem[_masasLastUpdatedField].ToString() );
            }

            if( spHubItem[_masasExpirationField] != null )
            {
                entry.Expiration = Convert.ToDateTime( spHubItem[_masasExpirationField].ToString() );
            }
        }
        
        /// <summary>
        /// Gets the registered hubs.
        /// </summary>
        /// <returns>
        /// The registered hubs.
        /// </returns>
        public List<Hub> GetRegisteredHubs()
        {
            List<Hub> hubs = new List<Hub>();

            if( SPWebService.ContentService != null )
            {
                foreach( SPWebApplication webApp in SPWebService.ContentService.WebApplications )
                {
                    foreach( SPSite site in webApp.Sites )
                    {
                        using( SPWeb web = site.OpenWeb() )
                        {
                            // Get the sites...
                            hubs.AddRange( GetHubsFromSites( web ) );
                        }
                    }
                }
            }
            else
            {
                Logger.AddLogEntry( "Could not connect to the SharePoint content service!", LogEventType.Error );
            }

            return hubs;
        }

        /// <summary>
        /// Gets the hubs from the sites.
        /// </summary>
        /// <param name="web">The SharePoint web object.</param>
        /// <returns>The hubs associated with the given SharePoint Web object.</returns>
        private List<Hub> GetHubsFromSites( SPWeb web )
        {
            List<Hub> hubs = new List<Hub>();
            SPWebCollection collWebsite = web.Webs;

            Logger.AddLogEntry( "Found Site: " + web.Title + " @ " + web.Url );
            hubs.AddRange( GetHubsFromLists( web ) );

            foreach( SPWeb subSite in collWebsite )
            {
                hubs.AddRange( GetHubsFromSites( subSite ) );
            }
            return hubs;
        }

        /// <summary>
        /// Gets the hubs from lists.
        /// </summary>
        /// <param name="web">The SharePoint web object.</param>
        /// <returns>The hubs associated with the given SharePoint Web object.</returns>
        private List<Hub> GetHubsFromLists( SPWeb web )
        {
            List<Hub> hubs = new List<Hub>();

            SPList hubList = web.Lists.TryGetList( _masasHubConnectionsListName );

            if( hubList != null )
            {
                List<Filter> filters = GetFilters( web );

                foreach( SPListItem spListItem in hubList.Items )
                {
                    SharePointHub hub = new SharePointHub();

                    hub.SiteUrl = web.Url;
                    hub.ItemIdentifier = Convert.ToInt32( spListItem[_spIDField].ToString() );
                    hub.Name = spListItem[_spTitleField].ToString();
                    hub.Token = spListItem[_masasTokenField].ToString();

                    SPFieldUrlValue urlValue = new SPFieldUrlValue( spListItem[_masasURIField].ToString() );
                    hub.URI = urlValue.Url;

                    if( spListItem[_masasLastAccessedField] == null )
                    {
                        hub.LastPolled = new DateTime();
                    }
                    else
                    {
                        hub.LastPolled = Convert.ToDateTime( spListItem[_masasLastAccessedField].ToString() );
                    }

                    SPFieldMultiChoiceValue accessValue = new SPFieldMultiChoiceValue( spListItem[_masasAccessRightsField].ToString() );
                    for( int i=0; i < accessValue.Count; i++ )
                    {
                        if( accessValue[i] == Hub.AccessType.Read.ToString() )
                        {
                            hub.Access = hub.Access | Hub.AccessType.Read;
                        }
                        if( accessValue[i] == Hub.AccessType.Write.ToString() )
                        {
                            hub.Access = hub.Access | Hub.AccessType.Write;
                        }
                    }
                    
                    hub.Filters = filters;
                    hubs.Add( hub );
                }
            }

            return hubs;
        }

        /// <summary>
        /// Gets the filters.
        /// </summary>
        /// <param name="web">The web.</param>
        /// <returns></returns>
        private List<Filter> GetFilters( SPWeb web )
        {
            List<Filter> filters = new List<Filter>();
            SPList hubFilters = web.Lists.TryGetList( _masasHubFiltersListName );
            if( hubFilters != null )
            {
                Logger.AddLogEntry( "Retrieving available filters." );

                foreach( SPListItem spListItem in hubFilters.Items )
                {
                    Filter filter = new Filter();

                    filter.Name = spListItem.Title;
                    filter.Description = ( spListItem[_masasFilterDescriptionField] != null ) ? spListItem[_masasFilterDescriptionField].ToString() : String.Empty;
                    filter.Priority = Convert.ToInt32( spListItem[_masasFilterPriorityField].ToString() );
                    filter.Enabled = Convert.ToBoolean( spListItem[_masasFilterEnabledField].ToString() );
                    filter.Value = spListItem[_masasFilterField].ToString();

                    switch( spListItem[_masasFilterTypeField].ToString() )
                    {
                        case "Entry":
                            filter.Type = FilterItemType.Entry;
                            break;
                        case "Alert":
                            filter.Type = FilterItemType.Alert;
                            break;
                        default:
                            Logger.AddLogEntry( "Unknown filter type: " + spListItem[_masasFilterTypeField].ToString() + "!", LogEventType.Warning );
                            filter.Type = FilterItemType.Unknown;
                            break;
                    }

                    switch( spListItem[_masasTrackingStateField].ToString() )
                    {
                        case "Action":
                            filter.SuccessValue = FilterStateType.Action;
                            break;
                        case "Monitor":
                            filter.SuccessValue = FilterStateType.Monitor;
                            break;
                        case "Ignore":
                            filter.SuccessValue = FilterStateType.Ignore;
                            break;
                        case "None":
                            filter.SuccessValue = FilterStateType.None;
                            break;
                        default:
                            Logger.AddLogEntry( "Unknown filter success value: " + spListItem[_masasTrackingStateField].ToString() + "!", LogEventType.Warning );
                            filter.SuccessValue = FilterStateType.None;
                            break;
                    }

                    filters.Add( filter );
                }
            }

            return filters;
        }

    }

}